use blip_buf::BlipBuf;

use super::{lengthcounter::LenghtCounter, volumeenvelope::VolumeEnvelope};

pub(crate) struct NoiseChannel {
    active: bool,
    dac_enabled: bool,
    reg_ff22: u8,
    length: LenghtCounter,
    period: u32,
    shift_width: u8,
    state: u16,
    delay: u32,
    last_amp: i32,
    pub(crate) blip: BlipBuf,
    pub(crate) volume_envelope: VolumeEnvelope,
}

impl NoiseChannel {
    pub(crate) fn new(blip: BlipBuf) -> Self {
        Self {
            active: false,
            dac_enabled: false,
            reg_ff22: 0,
            length: LenghtCounter::new(64),
            volume_envelope: VolumeEnvelope::new(),
            period: 2048,
            shift_width: 14,
            state: 1,
            delay: 0,
            last_amp: 0,
            blip: blip,
        }
    }

    pub(crate) fn rb(&self, addr: u16) -> u8 {
        match addr {
            0xFF20 => 0xFF,
            0xFF21 => self.volume_envelope.rb(addr),
            0xFF22 => self.reg_ff22,
            0xFF23 => {
                let tmp1 = 0x80;
                let tmp2 = if self.length.enabled { 0x40 } else { 0 };
                let tmp3 = 0x3F;
                tmp1 | tmp2 | tmp3
            }
            _ => unimplemented!("not support addr"),
        }
    }

    pub(crate) fn wb(&mut self, addr: u16, value: u8, frame_step: u8) {
        match addr {
            0xFF20 => self.length.set(value & 0x3F),
            0xFF21 => {
                self.dac_enabled = value & 0xF8 != 0;
                self.active = self.active && self.dac_enabled;
            }
            0xFF22 => {
                self.reg_ff22 = value;
                self.shift_width = if value & 8 == 8 { 6 } else { 14 };
                let freq_div = match value & 7 {
                    0 => 8,
                    n => n as u32 * 16,
                };

                self.period = freq_div << (value >> 4);
            }

            0xFF23 => {
                self.length.enable(value & 0x40 == 0x40, frame_step);
                self.active &= self.length.is_active();

                if value & 0x80 == 0x80 {
                    self.length.trigger(frame_step);
                    self.state = 0xFF;
                    self.delay = 0;

                    if self.dac_enabled {
                        self.active = true;
                    }
                }
            }

            _ => unimplemented!("not support addr"),
        }
        self.volume_envelope.wb(addr, value);
    }

    pub(crate) fn on(&self) -> bool {
        self.active
    }

    pub(crate) fn step_length(&mut self) {
        self.length.step();
        self.active &= self.length.is_active();
    }

    pub(crate) fn run(&mut self, start_time: u32, end_time: u32) {
        if !self.active {
            if self.last_amp != 0 {
                self.blip.add_delta(start_time, -self.last_amp);
                self.last_amp = 0;
                self.delay = 0;
            }
        } else {
            let mut time = start_time + self.delay;

            while time < end_time {
                let oldstate = self.state;
                self.state <<= 1;
                let bit = ((oldstate >> self.shift_width) ^ (self.state >> self.shift_width)) & 1;

                self.state |= bit;

                let amp = match (oldstate >> self.shift_width) & 1 {
                    0 => -(self.volume_envelope.volume as i32),
                    _ => self.volume_envelope.volume as i32,
                };

                if self.last_amp != amp {
                    self.blip.add_delta(time, amp - self.last_amp);
                    self.last_amp = amp;
                }

                time += self.period;
            }
            self.delay = time - end_time;
        }
    }
}
